package logger

import (
	"fmt"
	"os"
	"sync"

	"log"
	"path"
	"runtime"
	"strconv"
	"strings"
	"time"
)

const (
	LevelDebug = iota
	LevelInfo
	LevelWarning
	LevelError
)

// Name for support adapter
const (
	AdapterConsole   = "console"
	AdapterFile      = "file"
	AdapterMultiFile = "multifile"
)

const defaultAsyncMsgLen = 1e3 //缺省的chanel大小

type newLoggerFunc func() Logger

// gooselogger references the used application logger.
var gooselogger = NewLogger()

func GetDefaultLogger() *GooseLogger {
	return gooselogger
}

var adapters = make(map[string]newLoggerFunc)
var levelPrefix = [4]string{"[Debug] ", "[Info] ", "[Warn] ", "[Error] "}
var logMsgPool *sync.Pool

type GooseLogger struct {
	level               int //日志等级：debug,info,warn,error,缺省为warn
	lock                sync.Mutex
	init                bool //初始化完毕后赋值为true
	enableFuncCallDepth bool
	loggerFuncCallDepth int  // loggerFuncCallDepth is the number of stack frames
	asynchronous        bool //值为true表示开启异步支持以优化性能
	msgChanLen          int64
	msgChan             chan *logMsg //接收消息
	signalChan          chan string  //接收控制信号 目前只有flush 或 close
	wg                  sync.WaitGroup
	outputs             []*nameLogger
}

type nameLogger struct {
	Logger
	name string
}

type logMsg struct {
	level int
	msg   string
	when  time.Time
}

// Logger defines the behavior of a log provider.
type Logger interface {
	Init(config string) error
	WriteMsg(when time.Time, msg string, level int) error
	Destroy()
	Flush()
}

// NewLogger returns a new GooseLogger.
// channelLen means the number of messages in chan(used when asynchronous is true).
// if the buffering chan is full, logger adapters write to file or other way.
func NewLogger(channelLens ...int64) *GooseLogger {
	gl := new(GooseLogger)
	gl.level = LevelDebug
	gl.init = false
	gl.loggerFuncCallDepth = 4
	gl.msgChanLen = append(channelLens, 0)[0]
	gl.Async()
	gl.EnagleFuncCallDepth(true)
	if gl.msgChanLen <= 0 {
		gl.msgChanLen = defaultAsyncMsgLen
	}
	gl.signalChan = make(chan string, 1)
	return gl
}

var gooseLoggerMap = struct {
	sync.RWMutex
	logs map[string]*log.Logger
}{
	logs: map[string]*log.Logger{},
}

// Reset will remove all the adapter
func Reset() {
	gooselogger.Reset()
}

// GetLogger returns the default BeeLogger
func GetLogger(prefixes ...string) *log.Logger {
	prefix := append(prefixes, "")[0]
	if prefix != "" {
		prefix = fmt.Sprintf(`[%s] `, strings.ToUpper(prefix))
	}
	gooseLoggerMap.RLock()
	l, ok := gooseLoggerMap.logs[prefix]
	if ok {
		gooseLoggerMap.RUnlock()
		return l
	}
	gooseLoggerMap.RUnlock()
	gooseLoggerMap.Lock()
	defer gooseLoggerMap.Unlock()
	l, ok = gooseLoggerMap.logs[prefix]
	if !ok {
		l = log.New(gooselogger, prefix, 0)
		gooseLoggerMap.logs[prefix] = l
	}
	return l
}

// Error logs a message at error level.
func Error(f interface{}, v ...interface{}) {
	gooselogger.Error(formatLog(f, v...))
}

// Warn compatibility alias for Warning()
func Warn(f interface{}, v ...interface{}) {
	gooselogger.Warn(formatLog(f, v...))
}

// Info compatibility alias for Warning()
func Info(f interface{}, v ...interface{}) {
	gooselogger.Info(formatLog(f, v...))
}

// Debug logs a message at debug level.
func Debug(f interface{}, v ...interface{}) {
	gooselogger.Debug(formatLog(f, v...))
}

// EnagleFuncCallDepth enagle log funcCallDepth
func (gl *GooseLogger) EnagleFuncCallDepth(_b bool) {
	gl.enableFuncCallDepth = _b
}

func formatLog(f interface{}, v ...interface{}) string {
	var msg string
	switch f.(type) {
	case string:
		msg = f.(string)
		if len(v) == 0 {
			return msg
		}
		if strings.Contains(msg, "%") && !strings.Contains(msg, "%%") {
			//format string
		} else {
			//do not contain format char
			msg += strings.Repeat(" %v", len(v))
		}
	default:
		msg = fmt.Sprint(f)
		if len(v) == 0 {
			return msg
		}
		msg += strings.Repeat(" %v", len(v))
	}
	return fmt.Sprintf(msg, v...)
}

// SetLogger provides a given logger adapter into GooseLogger with config string.
// config need to be correct JSON as string: {"interval":360}.
func (gl *GooseLogger) setLogger(adapterName string, configs ...string) error {
	config := append(configs, "{}")[0]

	for _, l := range gl.outputs {
		if l.name == adapterName {
			if adapterName == AdapterConsole {
				//fmt.Println("Info: " + adapterName + " adapter has been registed.")
				return nil
			}
			return fmt.Errorf("logs: duplicate adaptername %q (you have set this logger before)", adapterName)
		}
	}

	newloggerFunc, ok := adapters[adapterName]
	if !ok {
		return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
	}

	lg := newloggerFunc()
	err := lg.Init(config)
	if err != nil {
		fmt.Fprintln(os.Stderr, "logs.GooseLogger.SetLogger: "+err.Error())
		return err
	}
	gl.outputs = append(gl.outputs, &nameLogger{name: adapterName, Logger: lg})
	return nil
}

// SetLogger provides a given logger adapter into GooseLogger with config string.
// config need to be correct JSON as string: {"interval":360}.
func (gl *GooseLogger) SetLogger(adapterName string, configs ...string) error {
	gl.lock.Lock()
	defer gl.lock.Unlock()
	if !gl.init {
		// if have not been init
		gl.outputs = []*nameLogger{}
		gl.init = true
	}
	return gl.setLogger(adapterName, configs...)
}

func (gl *GooseLogger) SetLevel(_level int) {
	if _level != LevelDebug && _level != LevelError && _level != LevelInfo && _level != LevelWarning {
		panic("SetLevel() fail:_level is wrong.")
	}
	gl.level = _level
}

// Register makes a log provide available by the provided name.
// If Register is called twice with the same name or if driver is nil,
// it panics.
func Register(name string, log newLoggerFunc) {
	if log == nil {
		panic("logs: Register provide is nil")
	}
	if _, dup := adapters[name]; dup {
		panic("logs: Register called twice for provider " + name)
	}
	adapters[name] = log
}

func (gl *GooseLogger) Warn(format string, v ...interface{}) {
	if LevelWarning < gl.level {
		return
	}
	err := gl.writeMsg(LevelWarning, format, v...)
	if err != nil {
		panic(err)
	}
}

func (gl *GooseLogger) Info(format string, v ...interface{}) {
	if LevelInfo < gl.level {
		return
	}
	err := gl.writeMsg(LevelInfo, format, v...)
	if err != nil {
		panic(err)
	}
}

func (gl *GooseLogger) Debug(format string, v ...interface{}) {
	if LevelDebug < gl.level {
		return
	}
	err := gl.writeMsg(LevelDebug, format, v...)
	if err != nil {
		panic(err)
	}
}

func (gl *GooseLogger) Error(format string, v ...interface{}) {
	if LevelError < gl.level {
		return
	}
	err := gl.writeMsg(LevelError, format, v...)
	if err != nil {
		panic(err)
	}
}

func (gl *GooseLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
	if !gl.init {
		gl.lock.Lock()
		err := gl.setLogger(AdapterConsole)
		if err != nil {
			panic(err)
		}
		gl.lock.Unlock()
	}

	if len(v) > 0 {
		msg = fmt.Sprintf(msg, v...)
	}
	when := time.Now()
	if gl.enableFuncCallDepth {
		_, file, line, ok := runtime.Caller(gl.loggerFuncCallDepth)
		if !ok {
			file = "???"
			line = 0
		}
		_, filename := path.Split(file) //拆分出文件名，去掉目录
		msg = "[" + filename + ":" + strconv.Itoa(line) + "] " + msg
	}

	//set level info in front of filename info
	msg = levelPrefix[logLevel] + msg

	if gl.asynchronous {
		lm := logMsgPool.Get().(*logMsg)
		lm.level = logLevel
		lm.msg = msg
		lm.when = when
		gl.msgChan <- lm
	} else {
		gl.writeToLoggers(when, msg, logLevel)
	}
	return nil
}

func (gl *GooseLogger) writeToLoggers(when time.Time, msg string, level int) {
	for _, l := range gl.outputs {
		err := l.WriteMsg(when, msg, level)
		if err != nil {
			fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err)
		}
	}
}

// Flush flush all chan data.
func (gl *GooseLogger) Flush() {
	if gl.asynchronous {
		gl.signalChan <- "flush"
		gl.wg.Wait()
		gl.wg.Add(1)
		return
	}
	gl.flush()
}

// Close close logger, flush all chan data and destroy all adapters in GooseLogger.
func (gl *GooseLogger) Close() {
	if gl.asynchronous {
		gl.signalChan <- "close"
		gl.wg.Wait()
		close(gl.msgChan)
	} else {
		gl.flush()
		for _, l := range gl.outputs {
			l.Destroy()
		}
		gl.outputs = nil
	}
	close(gl.signalChan)
}

// Reset close all outputs, and set gl.outputs to nil
func (gl *GooseLogger) Reset() {
	gl.Flush()
	for _, l := range gl.outputs {
		l.Destroy()
	}
	gl.outputs = nil
}

// SetLogger sets a new logger.
func SetLogger(adapter string, config ...string) error {
	return gooselogger.SetLogger(adapter, config...)
}

// EnableFuncCallDepth enable log funcCallDepth
func (gl *GooseLogger) EnableFuncCallDepth(b bool) {
	gl.enableFuncCallDepth = b
}
func EnableFuncCallDepth(b bool) {
	gooselogger.enableFuncCallDepth = b
}

// SetLogFuncCall set the CallDepth, default is 4
func SetLogFuncCall(b bool) {
	gooselogger.EnableFuncCallDepth(b)
	gooselogger.SetLogFuncCallDepth(4)
}

// SetLogFuncCallDepth set log funcCallDepth
func (gl *GooseLogger) SetLogFuncCallDepth(d int) {
	gl.loggerFuncCallDepth = d
}

// SetLogFuncCallDepth set log funcCallDepth
func SetLogFuncCallDepth(d int) {
	gooselogger.loggerFuncCallDepth = d
}

func (gl *GooseLogger) flush() {
	if gl.asynchronous {
		for {
			if len(gl.msgChan) > 0 {
				bm := <-gl.msgChan
				gl.writeToLoggers(bm.when, bm.msg, bm.level)
				logMsgPool.Put(bm)
				continue
			}
			break
		}
	}
	for _, l := range gl.outputs {
		l.Flush()
	}
}

// Async set the log to asynchronous and start the goroutine
func (gl *GooseLogger) Async(msgLen ...int64) *GooseLogger {
	gl.lock.Lock()
	defer gl.lock.Unlock()
	if gl.asynchronous {
		return gl
	}
	gl.asynchronous = true
	if len(msgLen) > 0 && msgLen[0] > 0 {
		gl.msgChanLen = msgLen[0]
	}
	gl.msgChan = make(chan *logMsg, gl.msgChanLen)
	logMsgPool = &sync.Pool{
		New: func() interface{} {
			return &logMsg{}
		},
	}
	gl.wg.Add(1)
	go gl.startLogger()
	return gl
}

// start logger chan reading.
// when chan is not empty, write logs.
func (gl *GooseLogger) startLogger() {
	gameOver := false
	for {
		select {
		case bm := <-gl.msgChan:
			//fmt.Println("i am startLogger() goroutine,i recieve msg :",bm)
			gl.writeToLoggers(bm.when, bm.msg, bm.level)
			logMsgPool.Put(bm)

		case sg := <-gl.signalChan:
			//fmt.Println("i am startLogger() goroutine,i recieve signal :",sg)

			// Now should only send "flush" or "close" to gl.signalChan
			gl.flush()
			if sg == "close" {
				for _, l := range gl.outputs {
					l.Destroy()
				}
				gl.outputs = nil
				gameOver = true
			}
			gl.wg.Done()
		}
		if gameOver {
			break
		}
	}
}

// levelLogLogger is defined to implement log.Logger
// the real log level will be LevelEmergency
const levelLoggerImpl = -1

func (gl *GooseLogger) Write(p []byte) (n int, err error) {
	if len(p) == 0 {
		return 0, nil
	}
	// writeMsg will always add a '\n' character
	if p[len(p)-1] == '\n' {
		p = p[0 : len(p)-1]
	}
	// set levelLoggerImpl to ensure all log message will be write out
	err = gl.writeMsg(levelLoggerImpl, string(p))
	if err == nil {
		return len(p), err
	}
	return 0, err
}
